# -*- coding: utf-8 -*-
'''
Created on 24 september 2012

@author: Fabien

usage : 
from site.ufs import UfsDict
UFS = UfsDict(annee="11-12", niveau="ss")
for num, uf in UFS.items():
    print uf

'''

# Imports génériques
from urllib import urlencode
from urllib2 import Request, urlopen, HTTPCookieProcessor, build_opener, install_opener
from cookielib import LWPCookieJar
import datetime
import time
import re
from HTMLParser import HTMLParser

# Imports spécifiques
from cfwb.BeautifulSoup import BeautifulSoup

# fonction d'aide pour convertir les HTML entities en unicode
e2c = HTMLParser().unescape

# Login et mot de passe d'accès au site derf.www.cfwb.be
import local_config
LOGIN = local_config.LOGIN
PASSWORD = local_config.PASSWORD
SCHOOLYEAR = local_config.SCHOOLYEAR

auth_url="http://www.derf.cfwb.be/derf/servlet/PromMgr.JSPController"
headers = {
           'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
           'Accept-Charset':'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
           'Accept-Encoding':'gzip,deflate,sdch',
           'Accept-Language':'fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4',
           'Connection':'keep-alive',
           'Host':'www.derf.cfwb.be',
           'Referer':'http://www.derf.cfwb.be/',
           'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1'
}
cj=LWPCookieJar()
cp=HTTPCookieProcessor(cj)
opener=build_opener(cp)
install_opener(opener)

def get_page(form_fields):
    """
    Retourne une page du site, lorsque l'on lui fournit un dictionnaire reprenant le nom des champs
    de formulaire et leurs valeurs, lève une exception "Not Connected" si l'on ne l'est pas...
    TODO : privatiser cette méthode... ou pas !
    """
    datas = urlencode(form_fields)
    auth_req=Request(auth_url,datas,headers)
    auth_page=urlopen(auth_req)
    page = auth_page.read()
    if page.find('value="Quitter"') == -1:
        raise Exception("Connection failed !")
    # Des pages incluent un mauvais attribut de tag "onclick", sans les guillemets.... correction pour BeautifulSoup
    p=re.compile(".*(?P<onclick>onclick=(?P<error>[^\"][^>]*))",re.S)
    m=p.match(page)
    if m:
        page = page.replace(m.group("onclick"),m.group("onclick").replace(m.group("error"),'"'+m.group("error")+'"'))
    return page

def connect(userId=LOGIN, password=PASSWORD):
    """
    Initialise une session sur le site et place le cookie d'identification
    """
    fields = {"userId":userId,"password":password,"FROMJSP":"UserView"}
    get_page(fields)
    
class ufs(dict):
    """
    Classe ufs, se comportant comme un dictionnaire python, dispose de deux attributs, 'niveau' et 'annee', dont la mise à jour provoque
    l'actualisation complète du dictionnaire sur le site. Les clef du dictionnaires sont les numéros administratifs des UFS.
    Les années vont de 91-92 à l'année actuelle...
    TODO : mettre Ufs à la place de ufs
    """
    def __init__(self, annee=SCHOOLYEAR, niveau=u"SI"):
        """
        1- Ouvre une session sur le site, récupère la page d'accueil et
        les différentes options pour le filtrage des documents annuels (site, niveau, etc.)
        """
        connect()
        # Initialise les options disponibles pour le choix des ufs
        self.options = {}
        fields = {"FROMJSP":"SelRubView","Page":"SelOrg"}
        page = get_page(fields)
        soup = BeautifulSoup(page)
        selects = soup.findAll("select")
        for s in selects:
            self.options[s['name']]={}
            opts = s.findAll("option")
            for opt in opts:
                self.options[s['name']][opt.string.strip()]=opt['value']
        self.__ecole = self.options['selectSchool'][u"9017001 - ECOLE INDUSTRIELLE ET COMMERCIALE D'AUVELAIS - AUVELAIS"]
        self.ecole = u"9017001 - ECOLE INDUSTRIELLE ET COMMERCIALE D'AUVELAIS - AUVELAIS"
        self.__regime = self.options['selectRegime'][u'Régime 1']
        self.regime = u"Régime 1"
        self.__niveau = self.options['selectNiveau'][niveau]
        self.__annee = self.options['selectSchoolYear'][annee]
        # Constitue la liste
        self.__create_list()

    def set_niveau(self, niveau):
        """Met à jour le niveau des ufs à consulter (eg: SS, SI, ...)"""
        self.__niveau = self.options['selectNiveau'][niveau]
        self.__create_list()
    
    def get_niveau(self):
        rd = dict([(y,x) for (x,y) in self.options['selectNiveau'].items()])
        return rd[self.__niveau]

    niveau = property(get_niveau, set_niveau)
    
    def set_annee(self, annee):
        """Met à jour l'année scolaire des ufs à consulter (eg: 10-11, 11-12, ...)"""
        self.__annee = self.options['selectSchoolYear'][annee]
        self.__create_list()
        
    def get_annee(self):
        rd = dict([(y,x) for (x,y) in self.options['selectSchoolYear'].items()])
        return rd[self.__annee]
    
    annee = property(get_annee, set_annee)

    def __create_list(self):
        """
        """
        fields = {
            "FROMJSP":"SelOrgView",
            "Page":"",
            "selectSchoolYear":self.__annee,
            "selectSchool":self.__ecole,
            "selectForm":"form1",
            "selectRegime":self.__regime,
            "selectNiveau":self.__niveau,
            "numadm1":"",
            "numadm2":"",
            "selectSel":"sel1",
            "selectSort":"sort1",
            "Submit":"Rechercher les formations"
        }
        try:
            page = get_page(fields).replace("&nbsp;","")
        except Exception:
            connect()
            page = get_page(fields).replace("&nbsp;","")
        # Ces pages incluent une déclaration "encoding" dans le DTD de la première ligne, ce qui est une erreur html et plante BeautifulSoup plus tard
        # cette portion retire cette mauvaise déclaration des pages concernées
        p=re.compile("<!DOCTYPE.*(?P<error> encoding=\"ISO-8859-1\")>")
        m=p.match(page)
        if m:
            page = page.replace(m.group(),m.group().replace(m.group('error'),""))
        soup = BeautifulSoup(page)
        formations_table = soup.findAll("table")[3]
        self.clear()
        for formation_line in formations_table.findAll("tr")[1:]:
            A,B,C,D,E = [x.font for x in formation_line.findAll("td")]
            if A.renderContents(): # si cette ligne décrit une UF
                # acquisition du numéro administratif
                num_adm = int(A.renderContents())
                
                # acquisition du libellé
                libelle = B.renderContents()
                pa = re.compile(".*(?P<nbh>\(\d+\.\d+\)).*") # Pour enlever le nombre de périodes entre parenthèses dans certains intitulés
                hr = pa.match(libelle)
                if hr:
                    libelle = libelle.replace(hr.group("nbh"),'')
                libelle = e2c(libelle.strip()) # enlève les espaces et convertit les entités HTML
                
                # acquisition du code 'dossier pédagogique'
                if getattr(C, 'a', None):
                    doc8 = C.a.renderContents()
                    # acquisition du numéro interne à la page pour aller sur le détail d'une uf (code détail)
                    code_detail = C.renderContents().split(",")[1]
                else:
                    doc8 = C.renderContents()
                    code_detail = None
                # création de l'objet uf
                if not self.get(num_adm):
                    new_uf = uf(num_adm,libelle,doc8,code_detail,self.niveau)
                    self[num_adm] = new_uf
                else:
                    new_uf = self[num_adm]
                
            if D.renderContents(): # si cette ligne décrit une organisation
                # mise à jour du code_detail si celui-ci n'existe pas encore
                if not new_uf.code_detail:
                    new_uf.code_detail = D.renderContents().split(",")[1]
                infos = D.a.renderContents().split()
                # acquisition du numéro de l'organisation
                norg = infos[0]
                # acquisition de la date de début
                dd=datetime.date(*time.strptime(infos[2],"%Y-%m-%d")[0:3])
                # acquisition de la date de fin
                df=datetime.date(*time.strptime(infos[4],"%Y-%m-%d")[0:3])
                # acquisition du statut de l'organisation (O-E-O-E-E...)
                statut = E.renderContents()
                # création et ajout de l'organisation
                new_uf_organisation = uf_organisation(new_uf, norg, dd, df, statut)
                new_uf.organisations.append(new_uf_organisation)
            
            if new_uf.code_detail:
                new_uf.get_details()

class uf(object):
        def __init__(self, num_adm, libelle, doc8, code_detail, niveau):
            self.num_adm=num_adm
            self.libelle=libelle
            self.doc8=doc8
            self.code_detail=code_detail
            self.niveau=niveau
            self.organisations=[]
            self.activites=[]
            self.ap_dd = None
            self.ap_de = None
            self.ad_dd = None
            self.ad_de = None
        def __repr__(self):
            base_repr = 80*"-" + "\n"
            base_repr += "%s | %s | %s" % (self.num_adm,self.libelle,self.doc8)
            if getattr(self, 'date_approbation', None):
                da = self.date_approbation.strftime("%d/%m/%Y")
            else:
                da = None
            if getattr(self, "type_uf", None):
                base_repr += """
                Type : %s
                Périodes : %s
                Date approbation doc8 : %s
                Code domaine : %s 
                Nom domaine : %s""" % (self.type_uf, self.periodes, da, self.code_domaine, self.nom_domaine)
            base_repr += """
                Nombre d'organisations : %s""" % (len(self.organisations),)
            if self.organisations:
                for o in self.organisations:
                    base_repr += """
                    org %s du %s au %s""" % (o.num_org, o.date_debut.strftime("%d/%m/%Y"), o.date_fin.strftime("%d/%m/%Y"))
            if self.ap_de:
                base_repr += """
                Admission provisoire avec effet le %s et dépêche le %s""" % (self.ap_de.strftime("%d/%m/%Y"), self.ap_dd.strftime("%d/%m/%Y"))
            if self.ad_de:
                base_repr += """
                Admission définitive avec effet le %s et dépêche le %s""" % (self.ad_de.strftime("%d/%m/%Y"), self.ad_dd.strftime("%d/%m/%Y"))
            base_repr += "\n" + 80*"-"
            return unicode(base_repr)

        def get_details(self):
            """
            Va sur la page de création d'une nouvelle UF pour collecter les infos sur le type de formation, la date d'approbation
            du Doc8, le nombre de périodes ainsi que le code et le nom du domaine de formation.
            Si il existe au moins une organisation, on peut également récuperer la situation des demandes d'admission...
            Ensuite, on récupère la décomposition pédagogique d'une UF en terme de périodes
            """
            
            if not self.code_detail:
                raise Exception("Informations innaccessibles pour cette uf (%s)" % self.num_adm)
                
            fields={
                "FROMJSP":"ListFormOrgView",
                "Page":"DetailOrg",
                "Nbr":self.code_detail,
                "Mode":"C"
            }
            page=get_page(fields)
            soup = BeautifulSoup(page)
            tables=soup.findAll("table")
            details=tables[2]
            detail=details.findAll("tr")[3:5]
            # acquisition du type de formation U(0), U(1), etc.
            infos=detail[0].td.font.renderContents().split("&nbsp;")
            self.type_uf=infos[1]
            # acquisition du nombre de périodes
            self.periodes=int(float(infos[4]))
            # acquisition de la date d'approbation du dossier pédagogique
            try:
                self.date_approbation=datetime.date(*time.strptime(infos[-1],"%Y-%m-%d")[0:3])
            except ValueError:
                self.date_approbation=None
            infos=detail[1].font.renderContents().split()
            # acquisition du code et du nom du domaine de formation
            self.code_domaine=infos[1].split(":")[0]
            self.nom_domaine=" ".join(infos[2:])
            # si il y a au moins une organisation, alors on peut aussi récupérer les infos de situation d'admission
            if self.organisations:
                fields={
                    "FROMJSP":"DetailOrgView",
                    "Page":"Doc1d",
                }
                page=get_page(fields)
                soup = BeautifulSoup(page.replace("&nbsp;",""))
                table = soup.findAll("table")[3]
                tag_ap = table.findAll("tr")[2]
                ap_dd = tag_ap.findAll("td")[2].font.renderContents().strip() # admission provisoire, date de dépêche
                ap_de = tag_ap.findAll("td")[4].font.renderContents().strip() # date d'effet
                tag_ad = table.findAll("tr")[1]
                ad_dd = tag_ad.findAll("td")[2].font.renderContents().strip() # admission définitive, date de dépêche
                ad_de = tag_ad.findAll("td")[4].font.renderContents().strip() # date d'effet
                if ap_dd : self.ap_dd = datetime.date(*time.strptime(ap_dd,"%Y-%m-%d")[0:3])
                if ap_de : self.ap_de = datetime.date(*time.strptime(ap_de,"%Y-%m-%d")[0:3])
                if ad_dd : self.ad_dd = datetime.date(*time.strptime(ad_dd,"%Y-%m-%d")[0:3])
                if ad_de : self.ad_de = datetime.date(*time.strptime(ad_de,"%Y-%m-%d")[0:3])

                # récupération de la composition d'une UFS en terme d'activités d'enseignement
                if not self.activites:
                    fields={
                        "FROMJSP":"DetailOrgView",
                        "Page":"Doc2",
                    }
                    page=get_page(fields)
                    soup = BeautifulSoup(page.replace("&nbsp;",""))
                    table = soup.findAll("table")[5]
                    branches = table.findAll("tr")[2:-1]
                    acts = []
                    for br in branches:
                        brd = br.findAll("td")
                        nb_periods = int(float((brd[5].font.renderContents())))
                        # Filtre pour ne garder que les parties avec un nombre de période <> 0
                        if nb_periods:
                            numero = int(float((brd[0].font.renderContents())))
                            categorie = brd[1].font.renderContents()
                            libelle = e2c(brd[2].font.renderContents()).strip()
                            acts.append((numero,categorie,libelle,nb_periods))
                    self.activites = acts

class uf_organisation(object):
    def __init__(self,uf, num_org, date_debut, date_fin, statut):
        self.uf = uf
        self.num_org = num_org
        self.date_debut = date_debut
        self.date_fin = date_fin
        self.statut = statut
        
    def __repr__(self):
        return "%s | %s | %s | %s | %s" % (self.uf.num_adm, self.num_org, self.date_debut.strftime("%d/%m/%Y"), self.date_fin.strftime("%d/%m/%Y"), self.statut)






